﻿using System.Security.Cryptography;
using System.Text;
using DoNet.Token.Entitys;
using Microsoft.Extensions.Configuration;

namespace DoNet.Token
{
    /// <summary>
    /// 
    /// </summary>
    public class CustomTokenService
    {
        /// <summary>
        /// 生成AccessToken及RefreshToken (存在的话旧返回已存在的)
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="secretKey"></param>
        /// <param name="accessExpire"></param>
        /// <param name="refreshExpire"></param>
        /// <returns></returns>
        public static AccessTokenOut GetTokenPair(string appId, string secretKey, int accessExpire, int refreshExpire, Dictionary<string, string> claims)
        {
            var model = TokenService.GetTokenByAppId(appId);
            if (model != null)
            {
                return new AccessTokenOut() { access_token = model.AccessToken, access_expire = model.AccessExpires, refresh_token = model.RefreshToken, refresh_expire = model.RefreshExpires };
            }
            else
            {
                string accessToken = GenerateToken(appId, secretKey, accessExpire, claims);
                string refreshToken = GenerateToken(appId, secretKey, refreshExpire * 24 * 60, new Dictionary<string, string>());

                TokenService.StoreToken(appId, accessToken, accessExpire, refreshToken, refreshExpire);
                return new AccessTokenOut() { access_token = accessToken, access_expire = accessExpire, refresh_token = refreshToken, refresh_expire = refreshExpire };
            }
        }

        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="secretKey"></param>
        /// <param name="expireIn"></param>
        /// <param name="claims"></param>
        /// <returns></returns>
        private static string GenerateToken(string appId, string secretKey, int expireIn, Dictionary<string, string> claims)
        {
            var payload = new StringBuilder();
            payload.Append($"appid={appId}|exp={DateTime.UtcNow.AddMinutes(expireIn).Ticks}");

            foreach (var claim in claims)
            {
                payload.Append($"|{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{claim.Key}={claim.Value}"))}");
            }

            using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(secretKey));
            var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload.ToString())));

            return $"{Convert.ToBase64String(Encoding.UTF8.GetBytes(payload.ToString()))}.{signature}";
        }

        /// <summary>
        /// 验证token
        /// </summary>
        /// <param name="token"></param>
        /// <returns>true 有效的(刷新)令牌；false 无效的(刷新)令牌</returns>
        public static bool ValidateToken(string token)
        {
            var claims = new Dictionary<string, string>();
            //判断是否已经吊销
            if (!TokenService.IsRevoked(token))
            {
                var parts = token?.Split('.');
                if (parts?.Length == 2)
                {
                    var bytes = Convert.FromBase64String(parts[0]);
                    var payload = Encoding.UTF8.GetString(bytes);
                    var appId = payload.Split('|')[0].Split('=')[1];
                    var expiryTicks = long.Parse(payload.Split('|')[1].Split('=')[1]);
                    if (DateTime.UtcNow.Ticks < expiryTicks)
                    {
                        var app = AppService.GetAppByAppId(appId);
                        if (app != null)
                        {
                            using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(app.AppSecret));
                            string oldSignature = parts[1];
                            string signature = Convert.ToBase64String(hmac.ComputeHash(bytes));
                            return oldSignature == signature;
                        }
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// 通过RefreshToken获取旧Token
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <param name="accessExpire"></param>
        /// <returns></returns>
        public static RefreshTokenOut? ForceRefresh(string refreshToken, int accessExpire)
        {
            var model = TokenService.GetTokenByRefreshToken(refreshToken);
            if (model != null)
            {
                string appId = GetAppId(model.AccessToken);
                var app = AppService.GetAppByAppId(appId);
                if (app != null)
                {
                    string newToken = GenerateToken(appId, app.AppSecret, accessExpire, ParseClaims(model.AccessToken));
                    TokenService.UpdateToken(refreshToken, newToken, accessExpire);  //更新令牌
                    TokenLogService.StoreTokenLog(model.AccessToken, newToken); //刷新令牌记录
                    return new RefreshTokenOut()
                    {
                        access_token = newToken,
                        access_expire = accessExpire
                    };
                }
            }
            return default;
        }

        /// <summary>
        /// 获取token的AppId
        /// </summary>
        /// <param name="token"></param>
        public static string GetAppId(string token)
        {
            var claims = new Dictionary<string, string>();
            var parts = token?.Split('.');
            if (parts?.Length == 2)
            {
                return Encoding.UTF8.GetString(Convert.FromBase64String(parts[0])).Split('|')[0].Split('=')[1];
            }
            return string.Empty;
        }

        /// <summary>
        /// 吊销所有token
        /// </summary>
        /// <param name="appId"></param>
        public static void ForceInvalidate(string appId)
        {
            TokenService.RevokeAllTokens(appId);
        }

        /// <summary>
        /// 获取token中的 Claims
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public static Dictionary<string, string> ParseClaims(string token)
        {
            var claims = new Dictionary<string, string>();
            var parts = token?.Split('.');
            if (parts?.Length == 2)
            {
                var payload = Encoding.UTF8.GetString(Convert.FromBase64String(parts[0]));
                var claimsArray = payload.Split('|');
                if (claimsArray.Length > 2)
                {
                    foreach (var pair in claimsArray.Skip(2))
                    {
                        var kv = Encoding.UTF8.GetString(Convert.FromBase64String(pair)).Split('=');
                        //payload.Append($"|{claim.Key}={Convert.ToBase64String(Encoding.UTF8.GetBytes(claim.Value))}");
                        claims[kv[0]] = kv[1];
                    }
                }
            }
            return claims;
        }
    }
}
